﻿using FundHelper.Model;
using FundHelper.Utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Linq;
using System.Net;
using System.Numerics;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using static OfficeOpenXml.ExcelErrorValue;

namespace FundHelper
{
    internal partial class ExploerData
    {


        /// <summary>
        /// 
        /// </summary>
        /// <param name="his">历史基金数据</param>
        /// <returns>返回(macds,dea,dif)元组</returns>
        private static IEnumerable<MacdPlusResult> CalMacdPlus(IEnumerable<ICanToTuple> his)
        {
            var tupleList = his.ToTuple();
            var macdResultArry = tupleList.GetMacdPlusResult(12, 26, 9);

            return macdResultArry;
        }
        private static MacdPlusChangeCollection CalMacdPlusChangePoint2(IEnumerable<ICanToTuple> his, int count)
        {
            var macdPlus = CalMacdPlus(his);
            return new MacdPlusChangeCollection(macdPlus );
        }

        private static IEnumerable<MacdPlusResult[]> CalMacdPlusChangePoint(IEnumerable<ICanToTuple> his, int count)
        {
            var macdPlus = CalMacdPlus(his).ToList();

            List<MacdPlusResult[]> results = new List<MacdPlusResult[]>();
            int plusAllCount = macdPlus.Count();
            var zzh = macdPlus.Where(a => a.LastDiffDay == 1);
            var zzhIdxes = zzh.Select(a => macdPlus.IndexOf(a) + 1);
            for (int i = 0; i < macdPlus.Count(); i++)
            {
                MacdPlusResult[] outPlus = new MacdPlusResult[count];
                outPlus[0] = macdPlus[i];
                var other = zzhIdxes.Where(a => a > i).ToList();

                for (int j = 0; j < other.Count() && j + 1 < count; j++)
                {
                    var idx = other[j];
                    if (idx < macdPlus.Count())
                    {
                        outPlus[j + 1] = macdPlus[idx];
                    }
                }
                results.Add(outPlus);
            }

            return results;
        }
        private static IReadOnlyDictionary<int, double[]> CalBIAS(IEnumerable<ICanToTuple> his, int[] periods)
        {
            Dictionary<int, double[]> result = new();
            for (int i = 0; i < periods.Length; i++)
            {
                int period = periods[i];

                double[] biases = his.ToTuple().GetBIASResult(period).Select(a => a.Value).ToArray();
                result.TryAdd(period, biases);
            }
            return result;
        }




        private static IReadOnlyDictionary<int, int[]> CalPeriodRank(IEnumerable<ICanToTuple> his, int[] periods)
        {
            Dictionary<int, int[]> result = new();
            for (int i = 0; i < periods.Length; i++)
            {
                int period = periods[i];
                int[] ranks = CalPeriodRank(his, period);
                result.TryAdd(period, ranks);
            }
            return result;
        }




        /// <summary>
        /// 
        /// </summary>
        /// <param name="his"></param>
        /// <param name="periods"></param>
        /// <returns>AF是时间递减。现在在最前面</returns>
        private static IReadOnlyDictionary<int, AFResult[]> CalPeriodAF(IEnumerable<ICanToTuple> his, int[] periods)
        {
            Dictionary<int, AFResult[]> result = new();
            for (int i = 0; i < periods.Length; i++)
            {
                int period = periods[i];
                AFResult[] afs= his.ToTuple().GetAFResult(period).ToArray();
                result.TryAdd(period, afs);
            }
            return result;
        }

        //数据归一化
        //private static void NormalizeData(IEnumerable<ICanToTuple> his, bool normalization)
        //{
        //    if (!normalization) return;
        //    for (int i = 0; i < his.Length - 90; i++)
        //    {
        //        var range = his.Take(new Range(i, i + 89));
        //        his[i].BIAS5 = PrepareData.Normalization(range.Select(a => a.BIAS5).ToList(), his[i].BIAS5);
        //        his[i].BIAS10 = PrepareData.Normalization(range.Select(a => a.BIAS10).ToList(), his[i].BIAS10);
        //        his[i].BIAS20 = PrepareData.Normalization(range.Select(a => a.BIAS20).ToList(), his[i].BIAS20);
        //    }
        //    his.ToList().ForEach(a => {
        //        if (a.Af == null)
        //            return;

        //    });
        //}

        private static List<Dictionary<string, string[]>> GetOutput(IEnumerable<ICanToTuple> quoteList, int outCount, int removeEnd, Dictionary<IndicatorEnum, ExtractionSetting> ExtactionSetting,out (Statistic<double> macd,Statistic<double> dea,Statistic<double> dif) macdStatic)
        {
            var result = new List<Dictionary<string, string[]>>();
            var head = new List<string>() { "id", "date", "dwjz" };
            StringBuilder headBuilder = new StringBuilder();

            ZZLResultCollection? periodsZZls=null;
            BIASResultCollection? biases = null;
            KDJResultCollection? kdjs = null;

            IReadOnlyDictionary<int, int[]>? periodsRanks=null; 
            IReadOnlyDictionary<int, AFResult[]>? periodsAFs=null;

            MacdPlusChangeCollection macdP=default;


            MacdResultCollection macdResultCollection=MacdResultCollection.Create(quoteList);
            //macds, dea, dif
            macdResultCollection.Deconstruct(out var difs,out var deas,out var macds);
            macdStatic = (macds.CalculateStatistic(), deas.CalculateStatistic(),difs.CalculateStatistic());

            #region 计算各类指标
            foreach (var item in ExtactionSetting) 
            {
                switch (item.Key)
                {
                    case IndicatorEnum.ZZL:
                        periodsZZls= (ZZLResultCollection)ZZLResultCollection.Create(quoteList, item.Value.period);
                        break;
                    case IndicatorEnum.BIAS:
                        biases =(BIASResultCollection)BIASResultCollection.Create(quoteList, new int[] {6,12,24});
                        break;
                    case IndicatorEnum.AF:
                        periodsAFs = CalPeriodAF(quoteList, item.Value.period);
                        break;
                    case IndicatorEnum.KDJ:
                        kdjs = KDJResultCollection.Create(quoteList);
                        break;
                    case IndicatorEnum.RANK:
                        periodsRanks = CalPeriodRank(quoteList, item.Value.period);
                        break;
                    case IndicatorEnum.MACDPLUSE:
                        macdP = CalMacdPlusChangePoint2(quoteList, item.Value.period.First());
                        break;
                    case IndicatorEnum.MACD or IndicatorEnum.MACDSta:
                    case IndicatorEnum.MACDCBIAS:
                    default:
                        break;
                }
            }
            #endregion
            var his = quoteList.ToQuote().OrderByDescending(a => a.Date).ToList();
            for (int i = removeEnd; i < outCount + removeEnd && i < his.Count() - 90; i++)
            {
                var dic=new Dictionary<string, string[]>();
                dic.Add("date",new string[] { his[i].Date.ToString("yyyyMMdd") });
                dic.Add("name", new string[] { his[i].Name });
                headBuilder.Append("date,id");
                foreach (var item in ExtactionSetting)
                {
                    var itemPeriod = item.Value.period;
                    var lagDays = item.Value.LagDay;
                    string[] info = item.Key switch
                    {
                        IndicatorEnum.ZZL when periodsZZls != null        => GetPeriodZzl(i, itemPeriod, periodsZZls),
                        IndicatorEnum.BIAS when biases is not null        => AppendBias(i, itemPeriod, lagDays, biases),
                        IndicatorEnum.MACD when macdResultCollection!=null=> AppendMacd(i, lagDays, difs, macds, deas),
                        IndicatorEnum.MACDSta when macds != null      => AppendMacdStatistic(i, itemPeriod, macds),
                        IndicatorEnum.MACDPLUSE when !macdP.IsDefault     => AppendMacdPlus(i, itemPeriod[0],macdP),
                        IndicatorEnum.KDJ when kdjs != null               => AppendKDJ(i, lagDays, kdjs),
                        IndicatorEnum.AF when periodsAFs != null          => AppendAF(i, itemPeriod, periodsAFs),
                        IndicatorEnum.RANK when periodsRanks != null      => AppendPeriodRank(i, itemPeriod, periodsRanks),
                        IndicatorEnum.MACDCBIAS                           => throw new NotImplementedException(),
                        _ => throw new NotImplementedException(),
                    };
                    dic.Add(item.Key.ToString(), info);
                }
                result.Add(dic);
               
            }

            return result;

            #region 添加周期指标

            string[] GetPeriodZzl(int rowIdx, int[] periods, ZZLResultCollection periodsZZls)
            {

                string[] lines =new string[periods.Length];
                for (int i = 0; i < periods.Length; i++)
                {
                    var period = periods[i];
                    if (periodsZZls.TryGetIndicators(period, out var zzls) && zzls.TryGet(rowIdx,out var zzl))
                        lines[i] = $"{zzl.Value}";
                }
                return lines;
            }

            string[] AppendPeriodRank(int rowIdx, int[] periods,IReadOnlyDictionary<int, int[]> periodsRanks)
            {
                string[] lines = new string[periods.Length];
                for (int i = 0; i < periods.Length; i++)
                {
                    var period = periods[i];
                    headBuilder.Append($",rank{period}");
                    if (periodsRanks[period].Length > rowIdx)
                        lines[i]=($"{periodsRanks[period][rowIdx]}");
                }
                return lines;
            }

            string[] AppendMacd(int rowIdx, int[] periods, double[] difs, double[] macds, double[] deas)
            {
                string[] lines = new string[periods.Length];
                int maxLen=Math.Min(Math.Min(difs.Length,macds.Length), deas.Length);
                for (int i = 0; i < periods.Length; i++)
                {
                    var period = periods[i];
                    int idx = rowIdx + period - 1;
                    if (idx >= maxLen)
                    {
                        lines[i] = ",,";
                        continue;
                    }
                    var macdRadio = difs[idx] / deas[idx];
                    lines[i] = ($"{macds[idx]},{macdRadio}");
                }
                return lines;
            }


            string[] AppendMacdStatistic(int rowIdx, int[] periods, double[] macds)
            {
                string[] lines = new string[periods.Length];
                for (int i = 0; i < periods.Length; i++)
                {
                    var period = periods[i];
                    if (rowIdx + period - 1 < macds.Length)
                    {
                        var stat = macds.AsSpan(rowIdx, period).CalculateStatistic();
                        lines[i] =stat.ToString();
                    }
                    else
                    {
                        lines[i] += $",,,,";
                    }
                }
                return lines;
            }

            string[] AppendMacdPlus(int rowIdx, int count, MacdPlusChangeCollection macdP)
            {
                string[] lines = new string[count];

                var macds = macdP.GetMacdPlus(rowIdx, count);
                for(int idx=0;idx<count;idx++)
                { 
                    var info = macds[idx];
                    lines[idx] = info != null ? $"{info.Area},{info.LastDiffDay}"
                                          : $",";
                }
                return lines;
            }
            string[] AppendKDJ(int rowIdx, int[] lagDays, KDJResultCollection kdjs)
            {
                string[] lines = new string[lagDays.Length];
                try
                {
                    var kdjInfo = kdjs.ExtractLag(rowIdx, lagDays);
                    int i = 0;
                    foreach ((double K, double D, double J) in kdjInfo)
                    {
                        lines[i] = ($"{K},{D},{J}");
                        i++;
                    }
                }
                catch (Exception)
                {
                    lines = Enumerable.Repeat(",,", lagDays.Length)
                        .ToArray();
                }
                return lines;
            }

            string[] AppendBias(int rowIdx, int[] periods, int[] lagDays,BIASResultCollection biases)
            {
                int lagLength = lagDays.Length;
                int perLength = periods.Length;
                string[] lines = new string[perLength*lagLength];
                int idx = 0;
                foreach (var period in periods)
                {
                    if (biases.TryGetIndicators(period,out IndicatorCollection<BIASResult>? collection))
                    {
                        var lagCol=(LagCollection<BIASResult>) collection;
                        var bias= lagCol.ExtractLag(rowIdx, lagDays);
                        foreach (var bia in bias)
                        {
                            lines[idx] = $"{bia.Value}";
                            idx++;
                        }
                    }
                    else
                    {
                        for(int i=0;i<lagLength;i++)
                        {
                            lines[idx] = "";
                            idx++;
                        }
                    }

                }
                return lines;
            }

            string[] AppendAF(int rowIdx, int[] periods,IReadOnlyDictionary<int, AFResult[]> periodsAFs)
            {
                string[] lines = new string[periods.Length];
                for (int i = 0; i < periods.Length; i++)
                {
                    var period = periods[i];
                    headBuilder.Append($",af{period},af{period}min,af{period}max");
                    if (periodsAFs[period].Length > rowIdx)
                    {
                        var af = periodsAFs[period][rowIdx];
                        lines[i]=($"{Cov(af.Value, $"af{period}")},{Cov(af.Min, $"af{period}min")},{Cov(af.Max, $"af{period}max")}");
                    }
                    else
                    {
                        lines[i]=(",,");
                    }
                }

                return lines.ToArray();
                int Cov(double value, string item)
                {
                    int result = 0;
                    
                    
                        
                    return result;
                }
            }
            #endregion
        }

        private static List<string> BuildOutputLines(List<Dictionary<string, string[]>> linesDic)
        {
            List<string> lines = new List<string>();
            foreach (var dic in linesDic) {
                string[] item= new string[dic.Values.Count];
                int i = 0;
                foreach (var d in dic.Values)
                {
                    string line = String.Join(',', d);
                    item[i] = line;
                    i++;
                }
                var t=String.Join(",",item);
                lines.Add(t);
            }
            return lines;
        
        }

        private static int[] CalPeriodRank(IEnumerable<ICanToTuple> tupleList, int period)
        {
            var hisList = tupleList.ToQuote().OrderByDescending(a => a.Date).ToList();
            int[] result = new int[hisList.Count() - period];
            for (int i = 0; i < result.Length; i++)
            {
                var temp = hisList.GetRange(i, period).OrderByDescending(a => a.Price).ToList();
                result[i]= temp.IndexOf(hisList[i]) + 1;
            }
            return result;
        }



        private static void WriteToFile(List<string> lines, string savePath, bool addHead,string head)
        {
            using (StreamWriter sw = new(new FileStream(savePath, FileMode.Append), Encoding.UTF8))
            {
                if (addHead)
                {
                    sw.WriteLine(head);
                }

                lines.ForEach(line => sw.WriteLine(line));
            }
        }
        private static async ValueTask WriteToFileAsync(List<string> lines, string savePath, bool addHead, string head)
        {
            using (var stream = new FileStream(savePath, FileMode.Append, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
            using (StreamWriter sw = new StreamWriter(stream, Encoding.UTF8))
            {
                if (addHead)
                {
                    await sw.WriteLineAsync(head);
                }
                foreach (var line in lines)
                {
                    await sw.WriteLineAsync(line);
                }
            }
        }
    }
}
